package com.planet_ink.ZipCodeWeather.resources;

import java.net.URLEncoder;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import com.planet_ink.ZipCodeWeather.model.WeatherForecast;
import com.planet_ink.ZipCodeWeather.model.WeatherForecasts;
import com.planet_ink.ZipCodeWeather.model.ZipCodeSet;
import com.planet_ink.ZipCodeWeather.services.HttpClientFactory;
import com.planet_ink.ZipCodeWeather.services.MiniJSON;
import com.planet_ink.ZipCodeWeather.services.MiniJSON.JSONObject;
import com.planet_ink.ZipCodeWeather.services.WeatherTransformer;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;


@Path("weather")
@Produces(MediaType.APPLICATION_JSON)
public class ZipCodeResource
{
	private static String singleQueryYQL	= "select item from weather.forecast where location = \"%s\"";
	private static String multiQueryYQL		= "select item from weather.forecast where location in (%s)";
	private static String resourcePrefix 	= "http://query.yahooapis.com/v1/public/yql?q=";
	private static String resourcePostfix 	= "&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";
	
	/**
	 * Returns Whether the given string is a valid U.S. zip code
	 * @param zipCode the potential zip code
	 * @return true if it is valid, false if not
	 */
	private boolean isValidZipCode(String zipCode)
	{
		if(zipCode == null)
		{
			return false;
		}
		zipCode=zipCode.trim();
		if(zipCode.length()!=5)
		{
			return false;
		}
		for(int i=0;i<zipCode.length();i++)
		{
			if(!Character.isDigit(zipCode.charAt(i)))
			{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Returns Whether all of the given strings are valid U.S. zip codes
	 * @param zipCodes the potential zip codes
	 * @return true if all are valid, false if not
	 */
	private boolean isValidZipCodes(String[] zipCodes)
	{
		if((zipCodes == null)||(zipCodes.length==0))
		{
			return false;
		}
		for(String zipCode : zipCodes)
		{
			if(!isValidZipCode(zipCode))
			{
				return false;
			}
		}
		return true;
	}
	
	@Path("{zipCode}")
	@GET
	/**
	 * A web resource accessed by /city/api/weather/{zipCode}
	 * Returns current, todays, and tomorrows weather conditions
	 * @param zipCode the zip code to get weather information about
	 * @return a JSON document containing weather conditions for this zip code
	 */
	public Response getWeatherSingle(@PathParam("zipCode") String zipCode)
	{
		if(!isValidZipCode(zipCode))
		{
			return Response.noContent().status(Status.BAD_REQUEST).build();
		}
		try
		{
			final String finalYQL = URLEncoder.encode(String.format(singleQueryYQL, zipCode),"UTF-8");
			Client httpClient = null;
			try
			{
				httpClient = HttpClientFactory.defaultInstance().getHttpClient();
				final WebResource webResource = httpClient.resource(resourcePrefix + finalYQL + resourcePostfix);
				final ClientResponse webResponse = webResource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
				if(webResponse.getStatus() != Status.OK.getStatusCode())
				{
					return Response.noContent().status(Status.fromStatusCode(webResponse.getStatus())).build();
				}
				final String jsonResponse = webResponse.getEntity(String.class);
				final MiniJSON jsonParser = new MiniJSON();
				final JSONObject json = jsonParser.parseObject(jsonResponse);
				final WeatherForecast conditions = WeatherTransformer.fromSingleYahooJSON(json);
				return Response.ok().entity(conditions).build();
			}
			finally
			{
				if(httpClient != null)
				{
					HttpClientFactory.defaultInstance().giveBackClient(httpClient);
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace(System.err);
			return Response.noContent().status(Status.INTERNAL_SERVER_ERROR).build();
		}
	}
	
	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	/**
	 * A web resource accessed by /city/api/weather
	 * Returns current, todays, and tomorrows weather conditions for all
	 * zip codes, in a map where the keys are the given zip codes
	 * @param zipCodes the zip codes to get weather information about
	 * @return a JSON document containing weather conditions about each zip code
	 */
	public Response getWeatherMultiple(ZipCodeSet zipCodes)
	{
		if((zipCodes == null)||(!isValidZipCodes(zipCodes.getZipcodes())))
		{
			return Response.noContent().status(Status.BAD_REQUEST).build();
		}
		try
		{
			final StringBuilder zipCodeList = new StringBuilder("");
			for(String zipCode : zipCodes.getZipcodes())
			{
				if(zipCodeList.length() > 0)
				{
					zipCodeList.append(",");
				}
				zipCodeList.append("\"").append(zipCode).append("\"");
			}
			final String finalYQL = URLEncoder.encode(String.format(multiQueryYQL, zipCodeList.toString()),"UTF-8");
			Client httpClient = null;
			try
			{
				httpClient = HttpClientFactory.defaultInstance().getHttpClient();
				final WebResource webResource = httpClient.resource(resourcePrefix + finalYQL + resourcePostfix);
				final ClientResponse webResponse = webResource.accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
				if(webResponse.getStatus() != Status.OK.getStatusCode())
				{
					return Response.noContent().status(Status.fromStatusCode(webResponse.getStatus())).build();
				}
				final String jsonResponse = webResponse.getEntity(String.class);
				final MiniJSON jsonParser = new MiniJSON();
				final JSONObject json = jsonParser.parseObject(jsonResponse);
				final WeatherForecasts conditions = WeatherTransformer.fromMultipleYahooJSON(zipCodes.getZipcodes(), json);
				return Response.ok().entity(conditions.getWeather()).build();
			}
			finally
			{
				if(httpClient != null)
				{
					HttpClientFactory.defaultInstance().giveBackClient(httpClient);
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace(System.err);
			return Response.noContent().status(Status.INTERNAL_SERVER_ERROR).entity(e).build();
		}
	}
}
